package com.simpou.commons.utils.tests;

import com.simpou.commons.utils.behavior.Validatable;
import com.simpou.commons.utils.behavior.Validator;
import com.simpou.commons.utils.exception.model.RuleViolation;
import com.simpou.commons.utils.lang.ArraysHelper;
import com.simpou.commons.utils.lang.Numbers;
import java.util.Arrays;

import org.junit.Assert;

import java.util.Collection;

/**
 * Extensão das assertivas de testes do JUnit.
 *
 * @author Jonas Pereira
 * @version 2013-02-08
 * @since 2012-10-05
 */
public class AssertExtension extends Assert {

    /**
     * @param collection Coleção vazia.
     * @throws AssertionError Se coleção não é vazia.
     */
    public static void assertEmpty(final Collection<?> collection) {
        assertNotNull(collection);

        if (!collection.isEmpty()) {
            throw new AssertionError("Collection is not empty, contains "
                    + collection.size() + " elements.");
        }
    }

    /**
     * @param collection Coleção não vazia.
     * @throws AssertionError Se coleção é vazia.
     */
    public static void assertNotEmpty(final Collection<?> collection) {
        assertNotNull(collection);

        if (collection.isEmpty()) {
            throw new AssertionError("Collection is empty.");
        }
    }

    /**
     * @param array Array vazio.
     * @throws AssertionError Se array não é vazio.
     */
    public static void assertEmpty(final Object[] array) {
        assertNotNull(array);

        if (array.length != 0) {
            throw new AssertionError("Array is not empty, contains "
                    + array.length + " elements.");
        }
    }

    /**
     * @param array Array não vazio.
     * @throws AssertionError Se array é vazio.
     */
    public static void assertNotEmpty(final Object[] array) {
        assertNotNull(array);

        if (array.length == 0) {
            throw new AssertionError("Array is empty.");
        }
    }

    /**
     * @param ref Valor de referência.
     * @param inf Valor limite inferior.
     * @param sup Valor limite superior.
     * @throws AssertionError Se valor de referência for menor que valor limite
     * inferior ou maior que o superior.
     */
    public static void assertInRange(final double ref, final double inf,
            final double sup) {
        if ((Numbers.compare(ref, inf) < 0)
                || (Numbers.compare(ref, sup) > 0)) {
            throw new AssertionError("Reference value " + ref
                    + " is not between " + inf + " and " + sup);
        }
    }

    /**
     * @param ref Valor de referência.
     * @param comp Valor a ser comparado com a a referência.
     * @throws AssertionError Se valor de referência for menor ou igual ao valor
     * comparado.
     */
    public static void assertGreaterThan(final double ref, final double comp) {
        if (Numbers.compare(ref, comp) < 1) {
            throw new AssertionError("Reference value " + ref
                    + " is lower than compared value " + comp);
        }
    }

    /**
     * @param ref Valor de referência.
     * @param comp Valor a ser comparado com a a referência.
     * @throws AssertionError Se valor de referência for maior ou igual ao valor
     * comparado.
     */
    public static void assertLowerThan(final double ref, final double comp) {
        if (Numbers.compare(ref, comp) > -1) {
            throw new AssertionError("Reference value " + ref
                    + " is greater than compared value " + comp);
        }
    }

    /**
     * @param fullString String a ser testada.
     * @param startString String pela qual o valor deve começar.
     * @throws AssertionError Se valor de referência não começar pelo
     * especificado.
     */
    public static void assertStartsWith(final String fullString,
            final String startString) {
        if (!fullString.startsWith(startString)) {
            throw new AssertionError("String \"" + fullString
                    + "\" doesnt starts with \"" + startString + "\"");
        }
    }

    /**
     * @param fullString String a ser testada.
     * @param endString String pela qual o valor deve terminar.
     * @throws AssertionError Se valor de referência não terminar com o
     * especificado.
     */
    public static void assertEndsWith(final String fullString,
            final String endString) {
        if (!fullString.endsWith(endString)) {
            throw new AssertionError("String \"" + fullString
                    + "\" doesnt ends with \"" + endString + "\"");
        }
    }

    /**
     * Verifica se todos elementos de uma coleção está presente em outra e
     * vice-versa.
     *
     * @param ref Coleção referência.
     * @param comp Outra coleção.
     * @throws AssertionError Caso arrays não sejam iguais em profuncdidade,
     * equals retorne true.
     */
    public static <T> void assertCollectionEquals(final Collection<T> ref, final Collection<T> comp) {
        if (ref == null) {
            if (comp != null) {
                throw new AssertionError("Expected null collection.");
            }
        } else {
            if (comp == null) {
                throw new AssertionError("Expected not null collection.");
            } else {
                if (comp.size() != ref.size()) {
                    throw new AssertionError("Expected "+ref.size()+" items but has "+comp.size());
                } else {
                    for (T t : ref) {
                        if (!comp.contains(t)) {
                            throw new AssertionError("Element "+t+" was not found.");
                        }
                    }
                }
            }
        }
    }

    /**
     * Verifica se todos elementos de dois arrays em uma mesma posição são
     * iguais.
     *
     * @param ref Objeto referência.
     * @param comp Outro objeto.
     * @throws AssertionError Caso arrays não sejam iguais em profuncdidade,
     * equals retorne true.
     */
    public static <T> void assertDeepEquals(final T[] ref, final T[] comp) {
        if (!Arrays.deepEquals(ref, comp)) {
            throw new AssertionError("Arrays " + ref + " and " + comp
                    + " are not deep equals.");
        }
    }

    /**
     * Verifica se dois objetos são diferentes.
     *
     * @param ref Objeto referência.
     * @param comp Outro objeto.
     * @throws AssertionError Caso objetos seja iguais, equals retorne true.
     */
    public static void assertNotEquals(final Object ref, final Object comp) {
        if (ref == null) {
            if (comp == null) {
                throw new AssertionError("Objects " + ref + " and " + comp
                        + " are equals.");
            }
        } else if (ref.equals(comp)) {
            throw new AssertionError("Objects " + ref + " and " + comp
                    + " are equals.");
        }
    }

    /**
     * Verifica se um objeto é válido.
     *
     * @param obj Objeto validável.
     * @throws AssertionError Caso objeto seja inválido.
     */
    public static void assertValid(final Validatable obj) {
        try {
            final RuleViolation violation = obj.validate();

            if (violation != null) {
                throw new AssertionError("Object is not valid. Cause: "
                        + violation.getMsg());
            }
        } catch (Throwable e) {
            throw new AssertionError("Object is not valid. Reason: "
                    + e.getMessage());
        }
    }

    /**
     * Verifica se um objeto é válido.
     *
     * @param obj Objeto a ser validado.
     * @param validator Validador compatível com o tipo do objeto.
     * @throws AssertionError Caso objeto seja inválido.
     */
    public static <T> void assertValid(final T obj, final Validator<T> validator) {
        try {
            final RuleViolation violation = validator.validate(obj);

            if (violation != null) {
                throw new AssertionError("Object is not valid. Cause: "
                        + violation.getMsg());
            }
        } catch (Throwable e) {
            throw new AssertionError("Object is not valid. Reason: "
                    + e.getMessage());
        }
    }
}
